/*
 *  Computer Graphics:  Andrew Hanson
 * GLUT RGB  Graphics template 
 *

 on Linux (X11) you could type:
 cc -o ps1.linux  ps1.c -lglut -lGLU -lGL  -lm
 
 on Mac OS X (Aqua) you could type:
 cc -o ps1.macosx ps1.c -framework OpenGL -framework GLUTma



 NOTE: This should work on default-X-configured Linux GRAPHICS SERVERS
 because it defines an RGB visual. 

 *
 *
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#if defined (__APPLE__) || defined (MACOSX)
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

/* Local Graphics Definitions  */
#include "defs.h"

/***************************************************************/
/******************** MY GLOBAL VARIABLES **********************/
/***************************************************************/

int FirstClear= GL_TRUE;

#define RUBBER_BAND_MODE 3
#define ZOOM_MODE 2
#define PIXEL_MODE 1

int myDrawingMode = 3;
/* 1 == pixel manipulations, 2 == zoom mode, 3 == rubberband line  */

int mySelectedRect = 1;
/* 1 == red rectangle, 2 == green rectangle, 3 == blue rectangle  */

GLfloat myZoomFactor = 1.0;

int mouseButtons = 0;

int MouseModifiers = 0;

int X=-1, Y=-1, dX=0, dY=0, mouse_down_loc_X, mouse_down_loc_Y;
int Xr=160, Yr=240, Xg=320, Yg=240, Xb=480, Yb=240;
int Xp = -1, Yp = -1;
GLfloat XC = 0.0, YC = 0.0, ZC = -10.0;
int main_window;
int main_height, main_width;
GLint draw= GL_FALSE;

#define imageHeight 20
#define imageWidth 30
GLubyte myPixelImage[imageHeight][imageWidth][3];

#define INITIAL_SQUARE_HALF_SIDE_LENGTH 20
int square_half_side;

int distToCenterFromMouseDownLocation = 0;
int screen_center_x, screen_center_y, diagonal_distance = 0;
;

/***************************************************************/
/*********************** MY UTIL FUNCTIONS *********************/
/***************************************************************/
// this will return a random number between 0 and 1
float getRandomNumber() {
	return (float) random() / (float) 0x7fffffff;
}

int calculateDiagonalDistance(int x1, int y1, int x2, int y2) {
	return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
	//printf("diagonal_distance = %d", diagonal_distance);
}
/***************************************************************/
/*********************** MOUSE CALLBACKS ***********************/
/***************************************************************/

/* Mouse Button  UP/DOWN  or DOWN/UP transition only */
void mouse(int button, int state, int x, int y) {
	button = (1 << button);

	X = x;
	Y = main_height - y - 1; /* Invert to right-handed system */
	Xp = X;
	Yp = Y;

	MouseModifiers = glutGetModifiers();
	/* bitmask with GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL, GLUT_ACTIVE_ALT */

	if (state == GLUT_DOWN) {
		mouseButtons |= button;

		mouse_down_loc_X = X;
		mouse_down_loc_Y = Y;

		if (myDrawingMode == ZOOM_MODE) {

			// this will be used in zoom mode
			distToCenterFromMouseDownLocation = calculateDiagonalDistance(X, Y,
					screen_center_x, screen_center_y);
		} else if (myDrawingMode == PIXEL_MODE) {
			draw = GL_TRUE;
		}
	} else {
		mouseButtons &= (~(button));
	}

	fprintf(stderr,"Comment this out: mouse buttons = [%d], modifiers = [%d] \n\
          state = %s, mouse at (%d,%d)\n",
	mouseButtons, MouseModifiers,
	state == GLUT_UP? "Just Up":"Just Down",x,y);

	glutPostRedisplay();
}

/* Mouse motion WHILE BUTTON DOWN  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
void motion(int x, int y) {
	fprintf(stderr,"Comment this out: mouse at (%d,%d)\n\
          mouse buttons = [%d], modifiers = [%d]\n",
	x,y, mouseButtons, MouseModifiers);

	/* .... your mouse-motion-related code here .... */

	X = x;
	Y = main_height - y - 1; /* Invert to right-handed system */
	Xp = X;
	Yp = Y;

	// fprintf(stderr,"X, Y = %d, %d \n",X,Y);


	switch (mouseButtons) {
	case 0x1: /* Left Mouse Button   */
		if (myDrawingMode == ZOOM_MODE) {
			FirstClear = GL_TRUE;

			// get the lenght from new location to center
			int distToCenter = calculateDiagonalDistance(X, Y, screen_center_x,
					screen_center_y);

			// check whether this is a zoom in or a zoom out
			// if the difference between (the distance from current location to center) and (mouse down location to
			// center) is positive, it is a zoom out. Zoom in if it is negative
			diagonal_distance = distToCenter
					- distToCenterFromMouseDownLocation;
		}
		break;
	case 0x2: /* Middle Mouse Button */
		break;
	case 0x4: /* Right Mouse Button  */
		break;
	}
	glutPostRedisplay();

}

/* Mouse motion WHILE ALL BUTTONS UP  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
void passive(int x, int y) {
	Xp = x;
	Yp = main_height - y - 1;
	/* .... ask for redisplay here only if necessary .... */
}

/* Handle Keyboard */
void keyboard(unsigned char key, int x, int y) {
	/*  Option: To force all lower case */
	/* if (key >= 'A' && key <= 'Z') key = (key-'A') + 'a'; */

	switch (key) {

	case 'z':
		FirstClear = GL_TRUE;
		myDrawingMode = ZOOM_MODE;
		fprintf(stdout,"Rectangle Zooming \n");
		break;
	case 'p':
		/* .... more code here .... */
		FirstClear = GL_TRUE;
		myDrawingMode = PIXEL_MODE;
		fprintf(stdout,"Pixel Operations On \n");
		break;
	case 'a':
		/* .... more code here .... */
		FirstClear = GL_TRUE;
		myDrawingMode = RUBBER_BAND_MODE;
		fprintf(stdout,"rubberband line\n");
		break;
	case 'q':
	case 27: /* 27 -> ESC */
		fprintf(stderr,"Normal Exit.\n");
		exit(EXIT_SUCCESS);
		break;
	default:
		fprintf(stderr,"Unhandled key: %c [%d] \n",key,key);
	}

	glutPostRedisplay();
}

/***************************************************************/
/************************** INIT GL ****************************/
/***************************************************************/

void initGL() {
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	FirstClear = GL_TRUE;
	/*     glEnable(GL_DEPTH_TEST);  */
}

/***************************************************************/
/************************ DRAW STUFF ***************************/
/***************************************************************/

/* ------------------------------- */
/* ----- pixel manipulations ----- */
/* ------------------------------- */

// this code extracted from GLProgramming Guide
void makeImage(void) {
	int i, j, c;

	for (i = 0; i < imageHeight; i++) {
		for (j = 0; j < imageWidth; j++) {
			c = ((((i&0x8)==0)^((j&0x8))==0))*255;
			myPixelImage[i][j][0] = (GLubyte) c;
			myPixelImage[i][j][1] = (GLubyte) c;
			myPixelImage[i][j][2] = (GLubyte) c;
		}
	}
}

void drawPixels() {
	/* .... more code here .... */
	if (mouseButtons == 0x1) {
		// left mouse button is down. Draw pixels using geometry
		glPointSize(getRandomNumber() * 10);
		glBegin(GL_POINT);
		glColor3f(getRandomNumber(), getRandomNumber(), getRandomNumber());
		glVertex2f(mouse_down_loc_X, mouse_down_loc_Y);
		glEnd();
	} else if (mouseButtons == 0x2) {
		// right mouse button is down. Draw pixels using bitmaps
		GLbyte data[5] = { 0x70, 0x40, 0x60, 0x40, 0x70 };
		glColor3f(getRandomNumber(), getRandomNumber(), getRandomNumber());
		glRasterPos2i(mouse_down_loc_X, mouse_down_loc_Y);
		glBitmap(6, 5, 0.0, 0.0, 8.0, 0.0, data);

	} else if (mouseButtons == 0x4) {
		// middle mouse button is down. Draw pixels using processor image
		makeImage();
		glPixelZoom(getRandomNumber(), getRandomNumber());
		glRasterPos2i(mouse_down_loc_X, mouse_down_loc_Y);
		glDrawPixels(imageWidth, imageHeight, GL_RGB, GL_UNSIGNED_BYTE, myPixelImage);
	}

} /* drawPixels() */

/* -------------------------- */
/* ----- color blending ----- */
/* -------------------------- */

void drawShape(int pX, int pY) {
	/* .... more code here .... */
}

void zoomRectangles() {

	//main_height = 42;

	for (square_half_side = INITIAL_SQUARE_HALF_SIDE_LENGTH; square_half_side
			< main_height / 2; square_half_side
			+= INITIAL_SQUARE_HALF_SIDE_LENGTH) {

		//		glRectf(screen_center_x - square_half_side - diagonal_distance,
		//				screen_center_y + square_half_side + diagonal_distance, screen_center_x + square_half_side + diagonal_distance,
		//				screen_center_y - square_half_side - diagonal_distance);

		// make sure we don't draw rectangles fallen on to the center
		if (square_half_side > -(diagonal_distance)) {

			glBegin(GL_LINE_LOOP); // Draw A Quad
			glVertex2f(screen_center_x - square_half_side - diagonal_distance,
					screen_center_y + square_half_side + diagonal_distance); // Top Left
			glVertex2f(screen_center_x + square_half_side + diagonal_distance,
					screen_center_y + square_half_side + diagonal_distance); // Top Right
			glVertex2f(screen_center_x + square_half_side + diagonal_distance,
					screen_center_y - square_half_side - diagonal_distance); // Bottom Right
			glVertex2f(screen_center_x - square_half_side - diagonal_distance,
					screen_center_y - square_half_side - diagonal_distance); // Bottom Left
			glEnd();
		}

	}

	// after a zoom operation, there can be space left in the center. Fill it. 
	if (diagonal_distance > INITIAL_SQUARE_HALF_SIDE_LENGTH) {

		for (square_half_side = diagonal_distance; square_half_side > 0; square_half_side
				-= INITIAL_SQUARE_HALF_SIDE_LENGTH) {
			glBegin(GL_LINE_LOOP); // Draw A Quad
			glVertex2f(screen_center_x - square_half_side, screen_center_y
					+ square_half_side); // Top Left
			glVertex2f(screen_center_x + square_half_side, screen_center_y
					+ square_half_side); // Top Right
			glVertex2f(screen_center_x + square_half_side, screen_center_y
					- square_half_side); // Bottom Right
			glVertex2f(screen_center_x - square_half_side, screen_center_y
					- square_half_side); // Bottom Left
			glEnd();
		}
	}

} /* zoomRectangles() */

/* --------------------------- */
/* ----- rubberband line ----- */
/* --------------------------- */
void drawRubberband() {
	/* ----- -----  draw rubberband line  ----- ----- */
	if (mouseButtons == 0x1) { // if mouse button is still down
		glColor3f(getRandomNumber(), getRandomNumber(), getRandomNumber());
		glBegin(GL_LINE);
		glVertex2f(mouse_down_loc_X, mouse_down_loc_Y);
		glVertex2f(X, Y);
		glEnd();
	}

	/* .... more code here .... */
} /* drawRubberband() */

/***************************************************************/
/************************ GLUT STUFF ***************************/
/***************************************************************/

void reshape(int w, int h) {
	fprintf(stderr,"Comment this out: reshape \n");
	w = Max(w,320);
	h = Max(h,240);
	main_height = h;
	main_width = w;

	screen_center_x = main_width / 2;
	screen_center_y = main_height / 2;

	glutReshapeWindow(w, h);
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, (double)Max(w,320), 0.0, (double)Max(h,240));

	glMatrixMode(GL_MODELVIEW);
	/*   glutPostRedisplay(); */
}

void visibility(int state) {
	/* May also want to check PARTIAL DAMAGE that
	 does not result in a call to the visibility callback:
	 if (glutLayerGet(GLUT_NORMAL_DAMAGED)) {
	 redraw = TRUE; }
	 */
	fprintf(stderr,"Comment this out: visibility: %s\n",
	(state==GLUT_VISIBLE)?"visible":"invisible");
	if (state == GLUT_VISIBLE) {
		glutPostRedisplay();
	}
}

void display(void) {
	fprintf(stderr,"Comment this out: display %d\n",myDrawingMode);

	if (FirstClear) {
		fprintf(stderr,"Clear\n");
		sleep(1);
		glClear(GL_COLOR_BUFFER_BIT);
		/*  sleep(1); does not work here, must preceed Clear  */
		FirstClear = GL_FALSE;
	}

	switch (myDrawingMode) {
	case 1: /* pixel manipulations */
		/* .... more code here .... */
		if (draw) {
			drawPixels();
			draw = GL_FALSE;
		}
		break;
	case 2: /* zoom mode */
		/* .... more code here .... */
		zoomRectangles();
		break;
	case 3: /* rubberband line */
		drawRubberband();

		break;
	default:
		fprintf(stderr,"Unhandled drawing mode: %d \n",myDrawingMode);
	}

	/*  glFinish();  */
	glFlush();

}

/***************************************************************/
/**************************** MAIN *****************************/
/***************************************************************/

int main(int argc, char **argv) {
	/* GLUT Initialization */
	glutInit(&argc, argv);
	glutInitWindowSize(640, 480);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	/* Create a new window and give a title to it */
	main_window = glutCreateWindow(":: PS1 : Eran Chinthaka ::");

	/* Initialize OpenGL stuff */
	initGL();

	/* Post Event Handlers */
	glutReshapeFunc(reshape);
	glutVisibilityFunc(visibility);
	glutDisplayFunc(display);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutPassiveMotionFunc(passive);

	glutKeyboardFunc(keyboard);
	fprintf(stderr,"Press ESC or 'q' to Exit.\n");

	glutIdleFunc(NULL);

	glutMainLoop();

	return (EXIT_SUCCESS);
}
